package com.bjkcst.baselib.utils;

import android.annotation.SuppressLint;
import android.os.Environment;
import android.util.Log;

import androidx.annotation.NonNull;

import com.bjkcst.baselib.utils.FileUtils;

import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class LogUtil {
    private static final String DIR_LOG = Environment.getExternalStorageDirectory() + "/KingKong/app/log/";
    // 日志压缩包后缀
    private static final String SUFFIX_LOG_ZIP = ".zip";
    // 日志后缀
    private static final String SUFFIX_LOG_TXT = ".txt";
    // 普通写日志-单个文件最大大小
    private static final long SINGLE_FILE_MAX_SIZE = 1024 * 1024 * 5;
    private static final int DAY = 86400000;
    private static final int BUFFER_LEN = 8192;

    private static int DEF_DELETE_LOG_COMPARE_DAY = 5;
    private static int DEF_DELETE_ZIP_COMPARE_DAY = 5;
    private static final int DEF_MIN_COMPARE_DAY = 0;
    private static final int DEF_MAX_COMPAREDAY = 2;
    private static final Pattern renameFilePattern = Pattern.compile("\\(\\d*\\)" + SUFFIX_LOG_TXT);
    private static final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();


    /**
     * 日志记录
     *
     * @param type    日志类型，同时命名文件名 type_yyyy-MM-dd_HH
     * @param message 日志内容
     */
    public static void save(String type, String message) {
        Log.i(type, message);
        saveOperationLog(type, message);
    }


    /**
     * 保存通信协议数据
     * @param type 根据此类型创建文件名称
     * @param data 协议数据
     */
    public static void protocol(String type, String data) {
        save(type, data);
    }


    /**
     * 操作日志
     * @param data 操作数据
     */
    public static void operation(String data) {
        save("operationLog", data);
    }

    /**
     * 设置删除周期
     * @param day 天
     */
    public static void setDeleteCompareDay(int day){
        DEF_DELETE_LOG_COMPARE_DAY = day;
        DEF_DELETE_ZIP_COMPARE_DAY = day;
    }


    /**
     * 定时删除日志及zip
     */
    public static void delete() {
        deleteLog(DEF_DELETE_LOG_COMPARE_DAY);
        deleteLogZip(DEF_DELETE_ZIP_COMPARE_DAY);
        mExecutorService.submit(() -> zipLog(DEF_MIN_COMPARE_DAY, DEF_MAX_COMPAREDAY));
    }

    /**
     * @author ChenGuiLin
     * @date 2022/4/13
     * @description 操作记录类日志
     * 普通方式-写入日志并按小时新建txt
     **/
    private static void saveOperationLog(String fileTypeStr, String log) {
        //跳过processTag、本层与上一调用层，记录更上层最多三层，每层间隔最小1层，记录调用路径
//        log = processTag(3, 3, 1) + "-" + log;
        saveLog(DIR_LOG + fileTypeStr + "_" + getCurDate("yyyy-MM-dd_HH") + SUFFIX_LOG_TXT, log);
    }

    /**
     * @author ChenGuiLin
     * @date 2022/4/13
     * @description 普通方式-保存日志
     **/
    private static void saveLog(String savePath, String log) {
        mExecutorService.submit(() -> {
            String savePathNew = checkLogFile(savePath, SINGLE_FILE_MAX_SIZE);
            writeFileFromString(FileUtils.getFileByPath(savePathNew), getCurDate("yyyy-MM-dd HH:mm:ss.SSS") + "-" + log + "\n", true);
        });
    }

    /**
     * @author ChenGuiLin
     * @date 2022/4/24
     * @description 文件过大则返回新文件名
     **/
    private static String checkLogFile(String savePath, long singleFileMaxSize) {
        int newIndex = 0;
        try {
            if (FileUtils.getLength(savePath) >= singleFileMaxSize) {
                if (renameFilePattern.matcher(savePath).find()) {//重命名格式匹配 xxx(1).txt  xxx(2).txt
                    newIndex = Integer.parseInt(savePath.substring(savePath.indexOf("(") + 1, savePath.indexOf(")")));
                    savePath = savePath.substring(0, savePath.indexOf("("));
                } else {
                    savePath = savePath.substring(0, savePath.indexOf("."));
                }
                savePath += "(" + (newIndex + 1) + ")" + SUFFIX_LOG_TXT;
                //递归检查
                return checkLogFile(savePath, singleFileMaxSize);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return savePath;
    }

    /**
     * @param startLayer:最小起始调用，0为processTag
     * @param sumLayer:最大记录数
     * @param step:层数记录最小间隔
     * @author ChenGuiLin
     * @date 2022/4/24
     * @description 调用者信息，线程名、类名、方法、行号
     **/
    private static String processTag(int startLayer, int sumLayer, int step) {
        if (startLayer < 0 || sumLayer < 1 || step < 1) {
            return "";
        }
        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        if (stackTrace == null || stackTrace.length <= startLayer) {
            return "";
        }
        //调用路径 #调用类1.方法1:所在行数.方法2:所在行数#调用类2.方法3:所在行数
        StringBuilder invocationPath = new StringBuilder();
        int endLayer = startLayer + (sumLayer - 1) * step;//最大结尾层数
        int maxLayer = Math.min(stackTrace.length - 1, endLayer);//嵌套层数最后下标
        int minLayer = Math.max(0, startLayer);//嵌套层数起始下标
        for (int i = maxLayer; i >= minLayer; i -= step) {//剔除当前方法与两层调用者
            StackTraceElement info = stackTrace[i];
            if (info == null) {
                continue;
            }
            String fileName = info.getFileName();
            if (fileName.contains(".")) {
                fileName = fileName.substring(0, fileName.indexOf("."));
            }
            if (!invocationPath.toString().contains(fileName)) {//#类文件名
                invocationPath.append("#").append(fileName);
            }
            String methodName = info.getMethodName();
            if (info.getMethodName().contains("$")) {//系统方法省略
                invocationPath.append(".$");
            } else {//.方法名:行号
                invocationPath.append(".").append(methodName).append(":").append(info.getLineNumber());
            }
        }
        //当前线程名
        String tName = Thread.currentThread().getName();
        //[当前线程名, 调用路径]
        return "[" + tName + ", " + invocationPath.toString() + "]";
    }


    /**
     * @param maxCompareDay：相差天数最小限制
     * @param minCompareDay：相差天数最大限制 if maxCompareDay<= minCompareDay  [minCompareDay, 正无穷)
     * @param fileSuffix：文件后缀
     * @author ChenGuiLin
     * @date 2022/4/27
     * @description 获取与当前时间的天数差[minCompareDay, maxCompareDay)的.txt列表
     **/
    private static List<File> getLogList(String folderPath, int minCompareDay, int maxCompareDay, String fileSuffix) {
        return FileUtils.listFilesInDirWithFilter(folderPath, new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                if (pathname == null || !pathname.exists() || !pathname.isFile() || !pathname.getName().contains(fileSuffix)) {
                    return false;
                }
                String fileName = pathname.getName();
                List<String> fileNameList = getMatches("\\d{4}-\\d{2}-\\d{2}", fileName);
                if (fileNameList == null || fileNameList.isEmpty()) {
                    return false;
                }
                long tempDay = getTimeSpan(getCurDate("yyyy-MM-dd"), fileNameList.get(0), getSafeDateFormat("yyyy-MM-dd"));
                if (maxCompareDay <= minCompareDay) {
                    return tempDay >= minCompareDay;
                }
                return tempDay >= minCompareDay && tempDay < maxCompareDay;
            }
        }, true);
    }

    private static long getTimeSpan(final String time1, final String time2, @NonNull final DateFormat format) {
        try {
            long curTime = format.parse(time1).getTime();
            long fileTime = format.parse(time2).getTime();
            long time = (curTime - fileTime) / DAY;
            return time;
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return -1;
    }


    /**
     * @param compareDay 多少天前
     * @author ChenGuiLin
     * @date 2022/4/27
     * @description deleteLog
     **/
    private static void deleteLog(int compareDay) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                List<File> fileList = getLogList(DIR_LOG, compareDay, -1, SUFFIX_LOG_TXT);
                deleteLog(fileList);
            }
        }).start();
    }

    /**
     * @param compareDay 多少天前
     * @author ChenGuiLin
     * @date 2022/4/27
     * @description deleteLogZip
     **/
    private static void deleteLogZip(int compareDay) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                List<File> fileList = getLogList(DIR_LOG, compareDay, -1, SUFFIX_LOG_ZIP);
                deleteLog(fileList);
            }
        }).start();
    }

    /**
     * @author ChenGuiLin
     * @date 2022/4/27
     * @description 删除log
     **/
    private static boolean deleteLog(List<File> fileList) {
        if (fileList == null || fileList.isEmpty()) {
            return false;
        }
        boolean isSucceed = true;
        for (int i = fileList.size() - 1; i >= 0; i--) {
            isSucceed &= FileUtils.delete(fileList.get(i));
        }
        return isSucceed;
    }


    /**
     * @param maxCompareDay：相差天数最小限制
     * @param minCompareDay：相差天数最大限制 if maxCompareDay<= minCompareDay  [minCompareDay, 正无穷)
     * @author ChenGuiLin
     * @date 2022/4/27
     * @description 压缩日志
     **/
    private static void zipLog(int minCompareDay, int maxCompareDay) {
        String zipPath = DIR_LOG + minCompareDay + "_" + maxCompareDay + "_" + getCurDate("yyyy-MM-dd") + SUFFIX_LOG_ZIP;
        if (!new File(zipPath).exists()) {
            List<File> fileList = getLogList(DIR_LOG, minCompareDay, maxCompareDay, SUFFIX_LOG_TXT);
            zipLog(zipPath, fileList);
        }
    }

    /**
     * @author ChenGuiLin
     * @date 2022/4/27
     * @description 压缩日志
     **/
    private static boolean zipLog(String zipPath, List<File> fileList) {
        if (fileList == null || fileList.isEmpty()) {
            return false;
        }
        boolean isSucceed = true;
        try {
            zipFiles(fileList, new File(zipPath), null);
        } catch (IOException e) {
            e.printStackTrace();
            isSucceed = false;
        }
        return isSucceed;
    }

    private static List<String> getMatches(final String regex, final CharSequence input) {
        if (input == null) {
            return Collections.emptyList();
        }
        List<String> matches = new ArrayList<>();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(input);
        while (matcher.find()) {
            matches.add(matcher.group());
        }
        return matches;
    }

    public static boolean zipFiles(final Collection<File> srcFiles, final File zipFile, final String comment) throws IOException {
        if (srcFiles == null || zipFile == null) {
            return false;
        }
        ZipOutputStream zos = null;
        try {
            zos = new ZipOutputStream(new FileOutputStream(zipFile));
            for (File srcFile : srcFiles) {
                if (!zipFile(srcFile, "", zos, comment)) {
                    return false;
                }
            }
            return true;
        } finally {
            if (zos != null) {
                zos.finish();
                zos.close();
            }
        }
    }

    private static boolean isSpace(final String s) {
        if (s == null) {
            return true;
        }
        for (int i = 0, len = s.length(); i < len; ++i) {
            if (!Character.isWhitespace(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    private static boolean zipFile(final File srcFile, String rootPath, final ZipOutputStream zos, final String comment) throws IOException {
        rootPath = rootPath + (isSpace(rootPath) ? "" : File.separator) + srcFile.getName();
        if (srcFile.isDirectory()) {
            File[] fileList = srcFile.listFiles();
            if (fileList == null || fileList.length <= 0) {
                ZipEntry entry = new ZipEntry(rootPath + '/');
                entry.setComment(comment);
                zos.putNextEntry(entry);
                zos.closeEntry();
            } else {
                for (File file : fileList) {
                    if (!zipFile(file, rootPath, zos, comment)) {
                        return false;
                    }
                }
            }
        } else {
            InputStream is = null;
            try {
                is = new BufferedInputStream(new FileInputStream(srcFile));
                ZipEntry entry = new ZipEntry(rootPath);
                entry.setComment(comment);
                zos.putNextEntry(entry);
                byte buffer[] = new byte[BUFFER_LEN];
                int len;
                while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) {
                    zos.write(buffer, 0, len);
                }
                zos.closeEntry();
            } finally {
                if (is != null) {
                    is.close();
                }
            }
        }
        return true;
    }

    private static boolean writeFileFromString(final File file, final String content, final boolean append) {
        if (file == null || content == null) {
            return false;
        }
        if (!FileUtils.createOrExistsFile(file)) {
            Log.e("FileIOUtils", "create file <" + file + "> failed.");
            return false;
        }
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter(file, append));
            bw.write(content);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (bw != null) {
                    bw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static final ThreadLocal<Map<String, SimpleDateFormat>> SDF_THREAD_LOCAL = new ThreadLocal<Map<String, SimpleDateFormat>>() {
        @Override
        protected Map<String, SimpleDateFormat> initialValue() {
            return new HashMap<>();
        }
    };

    @SuppressLint("SimpleDateFormat")
    private static SimpleDateFormat getSafeDateFormat(String pattern) {
        Map<String, SimpleDateFormat> sdfMap = SDF_THREAD_LOCAL.get();
        //noinspection ConstantConditions
        SimpleDateFormat simpleDateFormat = sdfMap.get(pattern);
        if (simpleDateFormat == null) {
            simpleDateFormat = new SimpleDateFormat(pattern);
            sdfMap.put(pattern, simpleDateFormat);
        }
        return simpleDateFormat;
    }

    private static String getCurDate(String pattern) {
        SimpleDateFormat safeDateFormat = getSafeDateFormat(pattern);
        String norStr = safeDateFormat.format(System.currentTimeMillis());
        return norStr;
    }

}

